package com.hqd.ch03.v23.aop.framework.autoproxy;

import com.hqd.ch03.v23.aop.Advisor;
import com.hqd.ch03.v23.aop.Pointcut;
import com.hqd.ch03.v23.aop.TargetSource;
import com.hqd.ch03.v23.aop.framework.AopInfrastructureBean;
import com.hqd.ch03.v23.aop.framework.ProxyFactory;
import com.hqd.ch03.v23.aop.framework.ProxyProcessorSupport;
import com.hqd.ch03.v23.aop.framework.adapter.AdvisorAdapterRegistry;
import com.hqd.ch03.v23.aop.framework.adapter.GlobalAdvisorAdapterRegistry;
import com.hqd.ch03.v23.aop.target.SingletonTargetSource;
import com.hqd.ch03.v23.aopalliance.aop.Advice;
import com.hqd.ch03.v23.aware.BeanFactoryAware;
import com.hqd.ch03.v23.config.BeanPostProcessor;
import com.hqd.ch03.v23.factory.BeanFactory;
import com.hqd.ch03.v23.factory.FactoryBean;
import com.hqd.ch03.v23.utils.ClassUtils;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Proxy;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements BeanFactoryAware, BeanPostProcessor {
    protected static final Object[] DO_NOT_PROXY = null;
    private final Set<String> targetSourcedBeans = Collections.newSetFromMap(new ConcurrentHashMap<>(16));
    private final Map<Object, Class<?>> proxyTypes = new ConcurrentHashMap<>(16);
    private final Map<Object, Boolean> advisedBeans = new ConcurrentHashMap<>(256);
    private BeanFactory beanFactory;
    private AdvisorAdapterRegistry advisorAdapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
    private String[] interceptorNames = new String[0];
    private boolean applyCommonInterceptorsFirst = true;

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        Class<?> beanClass = bean.getClass();
        Object cacheKey = getCacheKey(beanClass, beanName);
        /**
         * 该方法会多次调用，故而使用缓存
         */
        if (StringUtils.isNotBlank(beanName) || !this.targetSourcedBeans.contains(beanName)) {
            if (this.advisedBeans.containsKey(cacheKey)) {
                return null;
            }
            //注册通知到容器中
            if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }
        return null;
    }

    protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        return false;
    }

    public void setApplyCommonInterceptorsFirst(boolean applyCommonInterceptorsFirst) {
        this.applyCommonInterceptorsFirst = applyCommonInterceptorsFirst;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (bean != null) {
            Object cacheKey = getCacheKey(bean.getClass(), beanName);
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
        return null;
    }

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        /**
         * 已经代理过了
         */
        if (StringUtils.isNotBlank(beanName) && this.targetSourcedBeans.contains(beanName)) {
            return bean;
        }
        /**
         * 不需要增强
         */
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
            return bean;
        }
        /**
         * 1、基础设施类（即切面相关的类：Advice、Pointcut、Advisor、AopInfrastructureBean），这种类应该永远不被代理
         * 2、并且beanName不以 .ORIGINAL 结尾
         */
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }

        /**
         * 匹配对应的拦截器，如果匹配结果为空，则返回：DO_NOT_PROXY
         */
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
            this.advisedBeans.put(cacheKey, Boolean.TRUE);
            Object proxy = createProxy(
                    bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }

        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

    /**
     * 判断是否是aop相关的接口
     *
     * @param beanClass
     * @return
     */
    protected boolean isInfrastructureClass(Class<?> beanClass) {
        return Advice.class.isAssignableFrom(beanClass) ||
                Pointcut.class.isAssignableFrom(beanClass) ||
                Advisor.class.isAssignableFrom(beanClass) ||
                AopInfrastructureBean.class.isAssignableFrom(beanClass);
    }

    protected Object getCacheKey(Class<?> beanClass, String beanName) {
        if (StringUtils.isNotBlank(beanName)) {
            return (FactoryBean.class.isAssignableFrom(beanClass) ?
                    BeanFactory.FACTORY_BEAN_PREFIX + beanName : beanName);
        } else {
            return beanClass;
        }
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    protected Object createProxy(Class<?> beanClass, String beanName,
                                 Object[] specificInterceptors, TargetSource targetSource) {


        ProxyFactory proxyFactory = new ProxyFactory();
        proxyFactory.copyFrom(this);

        /**
         * 是否使用类代理
         */
        if (proxyFactory.isProxyTargetClass()) {
            if (Proxy.isProxyClass(beanClass) || ClassUtils.isLambdaClass(beanClass)) {
                for (Class<?> ifc : beanClass.getInterfaces()) {
                    proxyFactory.addInterface(ifc);
                }
            }
        } else {
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }

        Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
        proxyFactory.addAdvisors(Arrays.asList(advisors));
        proxyFactory.setTargetSource(targetSource);
        return proxyFactory.getProxy();
    }

    protected Advisor[] buildAdvisors(String beanName, Object[] specificInterceptors) {
        Advisor[] commonInterceptors = resolveInterceptorNames();

        List<Object> allInterceptors = new ArrayList<>();
        if (specificInterceptors != null) {
            if (specificInterceptors.length > 0) {
                allInterceptors.addAll(Arrays.asList(specificInterceptors));
            }
            if (commonInterceptors.length > 0) {
                if (this.applyCommonInterceptorsFirst) {
                    allInterceptors.addAll(0, Arrays.asList(commonInterceptors));
                } else {
                    allInterceptors.addAll(Arrays.asList(commonInterceptors));
                }
            }
        }

        Advisor[] advisors = new Advisor[allInterceptors.size()];
        for (int i = 0; i < allInterceptors.size(); i++) {
            advisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));
        }
        return advisors;
    }

    private Advisor[] resolveInterceptorNames() {
        BeanFactory bf = this.beanFactory;
        List<Advisor> advisors = new ArrayList<>();
        for (String beanName : this.interceptorNames) {
            Object next = bf.getBean(beanName);
            advisors.add(this.advisorAdapterRegistry.wrap(next));
        }
        return advisors.toArray(new Advisor[0]);
    }

    public void setInterceptorNames(String... interceptorNames) {
        this.interceptorNames = interceptorNames;
    }

    protected abstract Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName,
                                                             TargetSource customTargetSource);
}
